package com.ruoyi.workflow.service.impl;

import ch.qos.logback.core.read.ListAppender;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.common.core.domain.PageQuery;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.core.service.UserService;
import com.ruoyi.common.core.text.Convert;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.JsonUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.bean.BeanUtils;
import com.ruoyi.flowable.common.constant.ProcessConstants;
import com.ruoyi.flowable.common.enums.ProcessStatus;
import com.ruoyi.flowable.core.FormConf;
import com.ruoyi.flowable.core.domain.ProcessQuery;
import com.ruoyi.flowable.factory.FlowServiceFactory;
import com.ruoyi.flowable.utils.ModelUtils;
import com.ruoyi.flowable.utils.ProcessFormUtils;
import com.ruoyi.flowable.utils.ProcessUtils;
import com.ruoyi.flowable.utils.TaskUtils;
import com.ruoyi.system.service.impl.SysUserServiceImpl;
import com.ruoyi.workflow.domain.WfDeployForm;
import com.ruoyi.workflow.domain.vo.*;
import com.ruoyi.workflow.mapper.WfDeployFormMapper;
import com.ruoyi.workflow.mapper.WfFormMapper;
import com.ruoyi.workflow.service.IWfProcessService;
import com.ruoyi.workflow.service.IWfTaskService;
import lombok.RequiredArgsConstructor;
import org.flowable.bpmn.constants.BpmnXMLConstants;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.StartEvent;
import org.flowable.engine.TaskService;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.history.HistoricProcessInstanceQuery;
import org.flowable.engine.repository.Deployment;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.engine.repository.ProcessDefinitionQuery;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.task.api.Task;
import org.flowable.task.api.TaskQuery;
import org.flowable.task.api.history.HistoricTaskInstance;
import org.flowable.variable.api.history.HistoricVariableInstance;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.util.UrlPathHelper;

import java.util.*;
import java.util.stream.Collectors;

@Service
@RequiredArgsConstructor
public class WfProcessServiceImpl extends FlowServiceFactory implements IWfProcessService {
    private final WfDeployFormMapper deployFormMapper;
    private final IWfTaskService wfTaskService;
    private final TaskService taskServiceBean;
    private final UrlPathHelper mvcUrlPathHelper;
    private final UserService userService;

    /**
     * @param processQuery
     * @param pageQuery    分页参数
     * @return
     */
    @Override
    public TableDataInfo<WfDefinitionVo> selectPageStartProcessList(ProcessQuery processQuery, PageQuery pageQuery) {
        Page<WfDefinitionVo> page = new Page<>();
//        流程定义列表数据查询
        ProcessDefinitionQuery processDefinitionQuery = repositoryService.createProcessDefinitionQuery()
                .latestVersion()//最新版本
                .active()//查询激活状态的流程定义
                .orderByProcessDefinitionKey()
                .asc();
//        构建搜索条件
        ProcessUtils.buildProcessSearch(processDefinitionQuery, processQuery);
        long pageTotal = processDefinitionQuery.count();
        if (pageTotal <= 0) {
            return TableDataInfo.build();
        }
        int offset = pageQuery.getPageSize() * (pageQuery.getPageNum() - 1);
        List<ProcessDefinition> definitionList = processDefinitionQuery.listPage(offset, pageQuery.getPageSize());

        ArrayList<WfDefinitionVo> definitionVoList = new ArrayList<>();
        for (ProcessDefinition processDefinition : definitionList) {
            String deploymentId = processDefinition.getDeploymentId();
//            查询部署信息对象
            Deployment deployment = repositoryService.createDeploymentQuery().deploymentId(deploymentId).singleResult();
            WfDefinitionVo vo = new WfDefinitionVo();
            vo.setDefinitionId(processDefinition.getId());
            vo.setProcessKey(processDefinition.getKey());
            vo.setProcessName(processDefinition.getName());
            vo.setVersion(processDefinition.getVersion());
            vo.setDeploymentId(processDefinition.getDeploymentId());
            vo.setSuspended(processDefinition.isSuspended());
//           流程定义时间
            vo.setCategory(deployment.getCategory());
            vo.setDeploymentTime(deployment.getDeploymentTime());
            definitionVoList.add(vo);
        }
        page.setRecords(definitionVoList);
        page.setTotal(pageTotal);
        return TableDataInfo.build(page);
    }

    @Override
    public FormConf selectFormContent(String definitionId, String deployId, String procInsId) {
        BpmnModel bpmnModel = repositoryService.getBpmnModel(definitionId);
        if (ObjectUtil.isNull(bpmnModel)) {
            throw new RuntimeException("获取流程设计失败！");
        }
//        老样子，还是获取开始节点
        StartEvent startEvent = ModelUtils.getStartEvent(bpmnModel);
        WfDeployForm deployForm = deployFormMapper.selectOne(new LambdaQueryWrapper<WfDeployForm>()
                .eq(WfDeployForm::getDeployId, deployId)
                .eq(WfDeployForm::getFormKey, startEvent.getFormKey())
                .eq(WfDeployForm::getNodeKey, startEvent.getId())
        );
//      先进行反序列化,表单数据结构等等
        FormConf formConf = JsonUtils.parseObject(deployForm.getContent(), FormConf.class);
        if (ObjectUtil.isNull(formConf)) {
            throw new RuntimeException("获取流程表单失败！");
        }
        if (ObjectUtil.isNotEmpty(procInsId)) {
//            获取流程实例
            HistoricProcessInstance historicProcIns = historyService.createHistoricProcessInstanceQuery()
                    .processInstanceId(procInsId)
                    .includeProcessVariables()
                    .singleResult();
//            提出表单信息
            ProcessFormUtils.fillFormData(formConf, historicProcIns.getProcessVariables());
        }
        return formConf;
    }

    /**
     * 总结：
     * 查询一个流程定义对象，然后传送流程定义对象、流程变量过去，启动流程
     *
     * @param procDefId 流程定义id
     * @param variables 流程变量
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void startProcessByDefId(String procDefId, Map<String, Object> variables) {
        try {
//            获取流程定义对象
            ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
                    .processDefinitionId(procDefId)
                    .singleResult();
//            启动流程
            startProcess(processDefinition, variables);
        } catch (Exception e) {
            e.printStackTrace();
            throw new ServiceException("流程启动错误");
        }

    }


    /**
     * 查询我的流程
     * 总结：
     * 利用历史服务，查询出我启动的流程，构建好搜索条件，listPage查询出历史流程实例列表，
     * 遍历列表，在历史变量实例中根据流程实例id和实例变量名称查询出一个历史实例变量对象,并提取status值
     * 由于是赋值是返回给前端，所以如果从历史实例变量中查询出来即赋值，否则判断当前实例是否有结束时间，来判断是否结束
     *
     * @param processQuery
     * @param pageQuery    分页参数
     * @return
     */
    @Override
    public TableDataInfo<WfTaskVo> selectPageOwnProcessList(ProcessQuery processQuery, PageQuery pageQuery) {
        Page<WfTaskVo> page = new Page<>();
//         查询有当前用户启动的流程实例 按照流程实例的开始时间，降序排序
        HistoricProcessInstanceQuery historicProcessInstanceQuery = historyService.createHistoricProcessInstanceQuery()
                .startedBy(TaskUtils.getUserId())
                .orderByProcessInstanceStartTime()
                .desc();
//        构建搜索条件
        ProcessUtils.buildProcessSearch(historicProcessInstanceQuery, processQuery);
        int offset = pageQuery.getPageSize() * (pageQuery.getPageNum() - 1);
        List<HistoricProcessInstance> historicProcessInstances = historicProcessInstanceQuery
                .listPage(offset, pageQuery.getPageSize());
        page.setTotal(historicProcessInstanceQuery.count());
        ArrayList<WfTaskVo> taskVoList = new ArrayList<>();
        for (HistoricProcessInstance hisIns : historicProcessInstances) {
            WfTaskVo taskVo = new WfTaskVo();
//            获取流程状态
//            利用历史服务查询历史变量实例，经过实例id和实例变量名筛选流程变量对象（即获取流程的状态值）
            HistoricVariableInstance processStatusVariable = historyService.createHistoricVariableInstanceQuery()
                    .processInstanceId(hisIns.getId())
                    .variableName(ProcessConstants.PROCESS_STATUS_KEY)
                    .singleResult();
            String processStatus = null;
//            判断流程实例status是否为空，不为空赋值状态值
            if (ObjectUtil.isNotNull(processStatusVariable.getValue())) {
                processStatus = Convert.toStr(processStatusVariable.getValue());
            }
//            兼容旧流程
//            为空即：判断hisIns.getEndTime()时间为空时，即还没结束，否则已完成
            if (processStatus == null) {
                processStatus = ObjectUtil.isNull(hisIns.getEndTime()) ? ProcessStatus.RUNNING.getStatus() : ProcessStatus.COMPLETED.getStatus();
            }
            taskVo.setProcessStatus(processStatus);
            taskVo.setCreateTime(hisIns.getStartTime());
            taskVo.setFinishTime(hisIns.getEndTime());
            taskVo.setProcInsId(hisIns.getId());

//            计算耗时
            if (Objects.nonNull(hisIns.getEndTime())) {
                taskVo.setDuration(DateUtils.timeDistance(hisIns.getEndTime(), hisIns.getStartTime()));
            } else {
                taskVo.setDuration(DateUtils.timeDistance(DateUtils.getNowDate(), hisIns.getStartTime()));
            }
//            流程部署实例信息
            Deployment deployment = repositoryService.createDeploymentQuery()
                    .deploymentId(hisIns.getDeploymentId()).singleResult();
            taskVo.setDeployId(hisIns.getDeploymentId());
            taskVo.setProcDefId(hisIns.getProcessDefinitionId());
            taskVo.setProcDefName(hisIns.getProcessDefinitionName());
            taskVo.setProcDefVersion(hisIns.getProcessDefinitionVersion());
            taskVo.setCategory(deployment.getCategory());
//            当前所处流程
//            查询指定流程实例id的任务（包括那种关联的信息），
            List<Task> taskList = taskService.createTaskQuery()
                    .processInstanceId(hisIns.getId())
                    .includeIdentityLinks()
                    .list();
            if (CollUtil.isNotEmpty(taskList)) {
//                将taskList转换成stream流，对流中的每个对象获取Name名，并过去非空，再使用收集器将字符串已，串联起来
                taskVo.setTaskName(taskList.stream()
                        .map(Task::getName)
                        .filter(StringUtils::isNotEmpty)
                        .collect(Collectors.joining(",")));
            }
            taskVoList.add(taskVo);
        }
        page.setRecords(taskVoList);
        return TableDataInfo.build(page);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteProcessByIds(String[] instanceIds) {
        List<String> ids = Arrays.asList(instanceIds);
//        检验流程是否结束
        long activeInsCount = runtimeService.createProcessInstanceQuery()
                .processInstanceIds(new HashSet<>(ids))
                .active()
                .count();
        if (activeInsCount > 0) {
            throw new ServiceException("不允许删除进行中的流程实例");
        }
//        删除历史流程实例
        historyService.bulkDeleteHistoricProcessInstances(ids);

    }

    @Override
    public TableDataInfo<WfTaskVo> selectPageTodoProcessList(ProcessQuery processQuery, PageQuery pageQuery) {
        Page<WfTaskVo> page = new Page<>();
        TaskQuery taskQuery = taskService.createTaskQuery()
                .active()
//                查询任务的同时也将流程变量查询出来
                .includeProcessVariables()
//                待办查询审批人是当前用户的任务
                .taskCandidateOrAssigned(TaskUtils.getUserId())
//                按创建时间降序排序
                .orderByTaskCreateTime()
                .desc();
//        构建搜索条件
        ProcessUtils.buildProcessSearch(taskQuery, processQuery);
        page.setTotal(taskQuery.count());
        int offset = pageQuery.getPageSize() * (pageQuery.getPageNum() - 1);
        List<Task> taskList = taskQuery.listPage(offset, pageQuery.getPageSize());
        List<WfTaskVo> flowList = new ArrayList<>();
        for (Task task : taskList) {
            WfTaskVo flowTask = new WfTaskVo();
//            当前流程信息
            flowTask.setTaskId(task.getId());
            flowTask.setTaskDefKey(task.getTaskDefinitionKey());
            flowTask.setCreateTime(task.getCreateTime());
//            设置流程实例id
            flowTask.setProcDefId(task.getProcessDefinitionId());
            flowTask.setTaskName(task.getName());
//            获取流程定义（模型）信息
            ProcessDefinition pd = repositoryService.createProcessDefinitionQuery()
                    .processDefinitionId(task.getProcessDefinitionId())
                    .singleResult();
            flowTask.setDeployId(pd.getDeploymentId());
            flowTask.setProcDefName(pd.getName());
            flowTask.setProcDefVersion(pd.getVersion());
            flowTask.setProcInsId(task.getProcessInstanceId());

//            流程发起人信息（先查询出历史流程，再从中查询发起人信息）
            HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery()
                    .processInstanceId(task.getProcessInstanceId())
                    .singleResult();
            Long userId = Long.parseLong(historicProcessInstance.getStartUserId());
            String nickName = userService.selectUserNameById(userId);
            flowTask.setStartUserId(userId);
            flowTask.setStartUserName(nickName);

//            流程变量
            flowTask.setProcVars(task.getProcessVariables());

            flowList.add(flowTask);
        }
        page.setRecords(flowList);
        return TableDataInfo.build(page);
    }


    /**
     * 流程详情信息
     *
     * @param procInsId 流程实例ID
     * @param taskId    任务ID
     * @return
     */
    @Override
    public WfDeployVo queryProcessDetail(String procInsId, String taskId) {
        WfDetailVo detailVo = new WfDetailVo();
//        获取流程实例
        HistoricProcessInstance historicProcIns = historyService.createHistoricProcessInstanceQuery()
                .processInstanceId(procInsId)
                .includeProcessVariables()
                .singleResult();
        if (StringUtils.isNotBlank(taskId)) {
            HistoricTaskInstance taskIns = historyService.createHistoricTaskInstanceQuery()
                    .taskId(taskId)
                    .includeIdentityLinks()
                    .includeProcessVariables()
                    .singleResult();
            if (taskIns == null) {
                throw new ServiceException("没有可办理的任务！");
            }
            detailVo.setTaskFormData(currTaskFormData(historicProcIns.getDeploymentId(),taskIns));
        }
        return null;
    }

    /**
     * 获取当前任务流程表单信息
     * @param deployId 部署id
     * @param taskIns 任务id
     * @return
     */
    private FormConf currTaskFormData(String deployId,HistoricTaskInstance taskIns)
    {
        WfDeployFormVo deployFormVo = new WfDeployFormVo();
        WfDeployForm deployForm = deployFormMapper.selectOne(new LambdaQueryWrapper<WfDeployForm>()
                .eq(WfDeployForm::getDeployId, deployId)
                .eq(WfDeployForm::getFormKey, taskIns.getFormKey())
                .eq(WfDeployForm::getNodeKey, taskIns.getTaskDefinitionKey())
        );
        if (deployForm!=null)
        BeanUtils.copyBeanProp(deployForm,deployFormVo);
        if (ObjectUtil.isNotEmpty(deployFormVo)) {
            FormConf currTaskFormData = JsonUtils.parseObject(deployFormVo.getContent(), FormConf.class);
            if (currTaskFormData!=null) {
                currTaskFormData.setFormBtns(false);
                ProcessFormUtils.fillFormData(currTaskFormData,taskIns.getTaskLocalVariables());
                return currTaskFormData;
            }
        }
        return null;
    }

    /**
     * 启动流程实例
     * 总结：
     * 判断流程定义对象是否为空，或是否被挂起，获取当前用户id,并设置id到流程中，
     *
     * @param procDef   流程定义对象
     * @param variables 流程变量
     */
    private void startProcess(ProcessDefinition procDef, Map<String, Object> variables) {
        if (ObjectUtil.isNotNull(procDef) && procDef.isSuspended()) {
            throw new ServiceException("流程已经被挂起，请想激活流程");
        }
        String userIdStr = TaskUtils.getUserId();
//      identityService的功能是  设置流程发起人
        identityService.setAuthenticatedUserId(userIdStr);
//        initiator: 指明保存认证用户（authenticated user）ID用的变量名。在流程启动时，操作用户的ID会保存在这个变量中
        variables.put(BpmnXMLConstants.ATTRIBUTE_EVENT_START_INITIATOR, userIdStr);
//        设置流程状态为进行中
        variables.put(ProcessConstants.PROCESS_STATUS_KEY, ProcessStatus.RUNNING);
//        发起流程实例
        ProcessInstance processInstance = runtimeService.startProcessInstanceById(procDef.getId(), variables);
//        第一个用户任务为发起人，则自动完成任务
        wfTaskService.startFirstTask(processInstance, variables);


    }


}
